/*
 * Container.java  0.4.0 / Oct 26, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing;

import com.cute4j.swing.graphics.Color;
import com.cute4j.swing.graphics.Dimension;
import com.cute4j.swing.layouts.FlowLayout;
import com.cute4j.swing.layouts.LayoutManager;
import com.cute4j.swing.properties.ContainerProperties;

/**
 * Container is a component that contains other Swing components.
 *
 * @cute4j.native
 *    generate="header"
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public class Container extends Component implements ContainerProperties {

// Static Initialization. ------------------------------------------------------

  static {
    _initialize();
  }

// Package-Private Fields. -----------------------------------------------------

  Component[] components;
  int componentCount;
  LayoutManager layoutManager;

// Public Constructors. --------------------------------------------------------

  /**
   * Constructs new Container object.
   */
  public Container() {
    this(new FlowLayout());
  }

  /**
   * Constructs new Container object.
   *
   * @param layoutManager the layout manager
   */
  public Container(LayoutManager layoutManager) {
    components = new Component[1];
    this.layoutManager = layoutManager;
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Appends the specified component to the end of this container.
   *
   * @param component the component to be added
   *
   * @return the component argument
   */
  public Component add(Component component) {
    return addImpl(component, null, -1);
  }

  /**
   * Adds the specified component to this container at the given position.
   *
   * @param component the component to be added
   * @param index the position at which to insert the component, or <tt>-1</tt>
   *        to append the component to the end
   *
   * @return the component argument
   */
  public Component add(Component component, int index) {
    return addImpl(component, null, index);
  }

  /**
   * Appends the specified component to the end of this container.
   *
   * @param component the component to be added
   * @param constraints the layout constraints
   *
   * @return the component argument
   */
  public Component add(Component component, Object constraints) {
    return addImpl(component, constraints, -1);
  }

  /**
   * Inserts the specified component at the specified index.
   *
   * @param component the component to be added
   * @param constraints the layout constraints
   * @param index the component index
   *
   * @return the component argument
   */
  public Component add(Component component, Object constraints, int index) {
    return addImpl(component, constraints, index);
  }

  /**
   * Removes index specified component from this container.
   *
   * @param index the index of the component to be removed
   *
   * @return component at specified index
   */
  public Component remove(int index) {
    disposedCheck();
    checkIndex(index);
    Component component = components[index];
    if (layoutManager != null) {
      layoutManager.removeLayoutComponent(component);
    }
    removeChild(component);
    releaseChild(component);
    int numMoved = componentCount - index - 1;
    int halfCapacity = components.length >> 1;
    if (componentCount <= halfCapacity) {
      Component[] oldComponents = components;
      components = new Component[halfCapacity];
      if (numMoved > 0) {
        System.arraycopy(oldComponents, 0, components, 0, index);
        System.arraycopy(oldComponents, index + 1, components, index, numMoved);
        componentCount--;
      } else {
        System.arraycopy(oldComponents, 0, components, 0, --componentCount);
      }
    } else {
      if (numMoved > 0) {
        System.arraycopy(components, index + 1, components, index, numMoved);
      }
      components[--componentCount] = null;
    }
    invalidate();
    component.invalidate();
    return component;
  }

  /**
   * Removes the specified component from this container.
   *
   * @param component the component to be removed
   */
  public void remove(Component component) {
    disposedCheck();
    if (component != null) {
      final Component[] components = this.components;
      for (int i = componentCount; --i >= 0;) {
        if (components[i] == component) {
          remove(i);
        }
      }
    }
  }

  /**
   * Removes all the components from this container.
   */
  public void removeAll() {
    if (componentCount > 0) {
      final Component[] components = this.components;
      final LayoutManager layoutManager = this.layoutManager;
      if (layoutManager == null) {
        for (int i = componentCount; --i >= 0;) {
          Component component = components[i];
          releaseChild(component);
          removeChild(component);
          components[i] = null;
        }
      } else {
        for (int i = componentCount; --i >= 0;) {
          Component component = components[i];
          layoutManager.removeLayoutComponent(component);
          releaseChild(component);
          removeChild(component);
          components[i] = null;
        }
      }
      if (componentCount > 1) {
        this.components = new Component[1];
      }
      componentCount = 0;
      invalidate();
    }
  }

  /**
   * Returns the number of components in this container.
   *
   * @return the number of components in this container
   */
  public int getComponentCount() {
    return componentCount;
  }

  /**
   * Returns the component at specified index.
   *
   * @param index the index of the component
   *
   * @return the component at specified index
   *
   * @throws IndexOutOfBoundsException if index is out of range
   *         <tt>((index &lt; 0) | (index &gt;= getComponentCount()))</tt>.
   */
  public Component getComponent(int index) {
    checkIndex(index);
    return components[index];
  }

  /**
   * Returns all the components in this container.
   *
   * @return all the components in this container
   */
  public Component[] getComponents() {
    final Component[] components = new Component[componentCount];
    System.arraycopy(this.components, 0, components, 0, componentCount);
    return components;
  }

  /**
   * Returns the index of the specified component.
   *
   * @param component the component
   *
   * @return the index of the specified component
   */
  public int getComponentIndex(Component component) {
    if (component != null) {
      final Component[] components = this.components;
      for (int i = 0; i < componentCount; i++) {
        if (components[i] == component) {
          return i;
        }
      }
    }
    return -1;
  }

  /**
   * Returns layout manager of this component.
   *
   * @return layout manager of this component
   */
  public LayoutManager getLayout() {
    return layoutManager;
  }

  /**
   * Sets the layout manager for this container.
   *
   * @param layoutManager the layout manager
   */
  public void setLayout(LayoutManager layoutManager) {
    LayoutManager oldLayoutManager = this.layoutManager;
    if (oldLayoutManager == null) {
      if (layoutManager == null) {
        return;
      }
    } else {
      if (oldLayoutManager.equals(layoutManager)) {
        return;
      }
    }
    this.layoutManager = layoutManager;
    invalidate();
    firePropertyChange(PROPERTY_LAYOUT_MANAGER, oldLayoutManager,
                       layoutManager);
  }

  /**
   * Lays out this container.
   */
  public void doLayout() {
    if (layoutManager != null) {
      layoutManager.layoutContainer(this);
    }
  }

  /**
   * Sets the size of the container to fit the preferred size and layouts of its
   * subcomponents.
   */
  public void pack() {
    setSize(getPreferredSize());
  }

// Protected Methods. ----------------------------------------------------------

  /**
   * Adds new component to the container.
   *
   * @param component the component
   * @param constraints the constraints
   * @param index the component index
   *
   * @return added component
   */
  protected Component addImpl(Component component, Object constraints,
                              int index) { // NOPMD
    disposedCheck();
    boolean insertComponent = (index != -1);
    if (insertComponent) {
      checkIndex(index);
    } else {
      index = componentCount;
    }
    checkChild(component);
    if (component.parent != null) {
      if (component.parent == this) {
        return component;
      }
      component.parent.remove(component);
    }
    acquireChild(component);
    insertChild(component, constraints, index);
    ensureCapacity(componentCount + 1);
    final Component[] components = this.components;
    if (insertComponent) {
      System.arraycopy(components, index, components, index + 1,
                       componentCount - index);
    }
    components[index] = component;
    ++componentCount;
    if (layoutManager != null) {
      layoutManager.addLayoutComponent(component, constraints);
    }
    component.invalidate();
    invalidate();
    return component;
  }

// Overridden Methods. ---------------------------------------------------------

  /**
   * {@inheritDoc}
   */
  public Dimension getPreferredSize() {
    if (!preferredSizeSet && (!valid || (preferredSize == null))) {
      if (layoutManager == null) {
        preferredSize = super.getPreferredSize();
      } else {
        preferredSize = layoutManager.preferredLayoutSize(this);
      }
    }
    return preferredSize;
  }

  /**
   * {@inheritDoc}
   */
  public Dimension getMaximumSize() {
    if (maximumSize != null) {
      return maximumSize;
    }
    if (layoutManager != null) {
      return layoutManager.maximumLayoutSize(this);
    }
    return super.getMaximumSize();
  }

  /**
   * {@inheritDoc}
   */
  public Dimension getMinimumSize() {
    if (minimumSize != null) {
      return minimumSize;
    }
    if (layoutManager != null) {
      return layoutManager.minimumLayoutSize(this);
    }
    return super.getMinimumSize();
  }

  /**
   * {@inheritDoc}
   */
  public void setComponentOrientation(ComponentOrientation orientation) {
    super.setComponentOrientation(orientation);
    final Component[] components = this.components;
    final int componentCount = this.componentCount;
    for (int i = 0; i < componentCount; i++) {
      components[i].setComponentOrientation(orientation);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void validate() {
    if (!valid) {
      disposedCheck();
      doLayout();
      valid = true;
      final Component[] components = this.components;
      for (int i = componentCount; --i >= 0;) {
        components[i].validate();
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public void invalidate() {
    if (valid) {
      valid = false;
      if (layoutManager != null) {
        layoutManager.invalidateLayout(this);
      }
      if (parent == null) {
        if (visible) {
          disposedCheck();
          _validate(_delegate);
        }
      } else {
        if (parent.valid) {
          parent.invalidate();
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public void setBackground(Color background) {
    super.setBackground(background);
    clearCache();
  }

  /**
   * {@inheritDoc}
   */
  public void setForeground(Color background) {
    super.setForeground(background);
    clearCache();
  }

  /**
   * {@inheritDoc}
   */
  public void updateLocalization() {
    super.updateLocalization();
    final Component[] components = this.components;
    final int componentCount = this.componentCount;
    for (int i = 0; i < componentCount; i++) {
      components[i].updateLocalization();
    }
  }

  /**
   * {@inheritDoc}
   */
  public Component[] getOwnedComponents() {
    return getComponents();
  }

  /**
   * {@inheritDoc}
   */
  protected String paramString() {
    StringBuffer sb = new StringBuffer(super.paramString());
    if (layoutManager != null) {
      sb.append(",layout=");
      sb.append(layoutManager.getClass().getName());
    }
    return sb.toString();
  }

  void destroyComponent() {
    valid = false;
    final Component[] components = this.components;
    for (int i = componentCount; --i >= 0;) {
      Component component = components[i];
      releaseChild(component);
      component.dispose();
      components[i] = null;
    }
    componentCount = 0;
  }

  void clearCache() {
    super.clearCache();
    final Component[] components = this.components;
    final int componentCount = this.componentCount;
    for (int i = 0; i < componentCount; i++) {
      components[i].clearCache();
    }
  }

  /*!
   * @cute4j.native
   *    overridden="true"
   */
  void _visibleChanged(boolean visible) {
    super._visibleChanged(visible);
    if (visible && !valid) {
      validate();
    }
  }

  /*!
   * @cute4j.native
   *    overridden="true"
   */
  void _sizeChanged(int width, int height) {
    super._sizeChanged(width, height);
    if (visible) {
      validate();
    }
  }

  /*!
   * @cute4j.native
   *    overridden="true"
   */
  void _styleSheetChanged(boolean removed) {
    super._styleSheetChanged(removed);
    final Component[] components = this.components;
    final int componentCount = this.componentCount;
    for (int i = 0; i < componentCount; i++) {
      components[i]._styleSheetChanged(removed);
    }
  }

// Package-Private Methods. ----------------------------------------------------

  void checkIndex(int index) {
    if ((index < 0) || (index >= componentCount)) {
      throw new ArrayIndexOutOfBoundsException("Invalid index: " + index);
    }
  }

  void checkChild(Component child) {
    if (child == null) {
      throw new IllegalArgumentException("Adding null child");
    }
    if (child.isDisposed()) {
      throw new IllegalArgumentException("Adding disposed child");
    }
    if (child instanceof Container) {
      if (child instanceof Window) {
        throw new IllegalArgumentException("Adding a window to a container");
      }
      for (Container container = this; container != null;
           container = container.parent) {
        if (container == child) { // NOPMD
          throw new IllegalArgumentException(
                                        "Adding container's parent to itself");
        }
      }
    }
  }

  void acquireChild(Component child) {
    acquireComponent(child);
    child.parent = this;
    child.windowShown = true;
    child.clearCache();
  }

  void releaseChild(Component child) {
    releaseComponent(child);
    child.parent = null;
    child.windowShown = false;
    child.clearCache();
  }

  /**
   * Inserts new child.
   *
   * @param child the child
   * @param constraints the layout constraints
   * @param index the child index
   */
  void insertChild(Component child, Object constraints, int index) {
    _insertChild(_delegate, child._delegate, index);
  }

  void removeChild(Component child) {
    _removeChild(_delegate, child._delegate);
  }

  void ensureCapacity(int minCapacity) {
    final int oldCapacity = components.length;
    if (minCapacity > oldCapacity) {
      final Component[] oldComponents = components;
      int newCapacity = (3 * oldCapacity) >> 1;
      if (++newCapacity < minCapacity) {
        newCapacity = minCapacity;
      }
      components = new Component[newCapacity];
      System.arraycopy(oldComponents, 0, components, 0, componentCount);
    }
  }

// Package-Private Native Methods. ---------------------------------------------

  native long _create();

  native void _insertChild(long delegate, long childDelegate, int index);

  native void _removeChild(long delegate, long childDelegate);

  native void _validate(long delegate);

// Private Static Native Method. -----------------------------------------------

  private static native void _initialize();

}
